#pragma once
#include <unistd.h>
#include <string>
#include <pthread.h>
#include <iostream>
#include <functional>
#include <cerrno>

namespace ThreadModule
{
    class ThreadData
    {
    public:
        ThreadData(const std::string &message = "innominate-thread") : _message(message) {}
        ThreadData(std::string &&message) : _message(std::move(message)) {}

        ~ThreadData() = default;

    public:
        std::string getMessage()
        {
            return _message;
        }
        void task()
        {
            std::cout << _message << " ... ";
        }

    private:
        std::string _message;
    };

    class Thread
    {
    public:
        explicit Thread() noexcept : _tid(), _joinable(false) {}

        template <class Func, class... Args>
        explicit Thread(const std::string& name, Func &&f, Args &&...args): _data(name)
        {
            auto wrapper = new std::pair<std::function<void()>, ThreadData>(
                std::bind(std::forward<Func>(f), std::forward<Args>(args)...), _data);

            if (pthread_create(&_tid, nullptr, &Thread::invoke_function, wrapper) != 0)
            {
                // error    (take care of memory leak)
                delete wrapper;
                throw std::system_error(errno, std::generic_category());
            }
            else
            {
                // success  (pthread has been created)
                _joinable = true;
            }
        }

        ~Thread()
        {
            if (_joinable)
            {
                pthread_detach(_tid);
            }
        }

        Thread(Thread &&other) noexcept : _tid(other._tid), _joinable(other._joinable)
        {
            other._joinable = false;
        }

        Thread &operator=(Thread &&other) noexcept
        {
            if (_joinable)
            {
                pthread_detach(_tid);
            }
            _tid = other._tid;
            _joinable = other._joinable;
            other._joinable = false;
            return *this;
        }

        Thread(const Thread &) = delete;
        Thread &operator=(const Thread &) = delete;

        void join()
        {
            if (!_joinable)
            {
                throw std::system_error(EINVAL, std::generic_category());
            }
            pthread_join(_tid, nullptr);
            _joinable = false;
        }

        void detach()
        {
            if (!_joinable)
            {
                throw std::system_error(EINVAL, std::generic_category());
            }
            pthread_detach(_tid);
            _joinable = false;
        }

        bool joinable() const noexcept
        {
            return _joinable;
        }

        pthread_t native_handle()
        {
            return _tid;
        }

        std::string getMessage()
        {
            return _data.getMessage();
        }

    private:
        pthread_t _tid;
        bool _joinable;
        ThreadData _data;

        static void* invoke_function(void* arg)     // static-> avoid implicit 'this' argument passing to invoke_function
        {
            auto wrapper = static_cast<std::pair<std::function<void()>, ThreadData>*>(arg);

            auto tmpdata = wrapper->second;
            tmpdata.task();

            (wrapper->first)();     // call handler function

            delete wrapper;
            return nullptr;
        }
    };
}
